package cn.uidcc.fast.jackson.util;

import org.apache.commons.lang3.StringUtils;

import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.*;
import java.util.regex.Pattern;

public class JSONDateFormat extends DateFormat {

    protected final static Map<String, String> dateRegFormat = new HashMap<>();

    protected final static TimeZone DEFAULT_TIMEZONE = DateUtils.DEFAULT_TIMEZONE;

    protected final static Locale DEFAULT_LOCALE = DateUtils.DEFAULT_LOCALE;

    protected final static Calendar CALENDAR = DateUtils.CALENDAR;

    protected final static JSONDateFormat instance = new JSONDateFormat();

    static {
        dateRegFormat.put("^\\d{8}$", DateUtils.DATE_STR);
        dateRegFormat.put("^\\d{14}$", DateUtils.DATE_KEY_STR_FULL);
        dateRegFormat.put("^\\d{15}$", DateUtils.DATE_MILLISECOND_STR);
        dateRegFormat.put("^\\d{17}$", DateUtils.DATE_MILLISECOND_STR_FULL);
        dateRegFormat.put("^\\d{4}[-]\\d{2}[-]\\d{2}$", DateUtils.DATE_SMALL_STR);
        dateRegFormat.put("^\\d{4}[-]\\d{2}[-]\\d{2}\\s\\d{2}[:]\\d{2}[:]\\d{2}$", DateUtils.DATE_FULL_STR);
        dateRegFormat.put("^\\d{4}[-]\\d{2}[-]\\d{2}\\s\\d{2}[:]\\d{2}[:]\\d{2}\\.\\d{1,3}$", DateUtils.DATE_FULL_STR);
        dateRegFormat.put(DateUtils.PATTERN_ISO8601, DateUtils.DATE_ISO8601_STR);
    }

    protected transient TimeZone _timezone;

    protected final Locale _locale;

    public JSONDateFormat() {
        _timezone = DEFAULT_TIMEZONE;
        _locale = DEFAULT_LOCALE;
        setCalendar(CALENDAR);
    }

    @Override
    public StringBuffer format(Date date, StringBuffer stringBuffer, FieldPosition fieldPosition) {
        TimeZone tz = DateUtils.DEFAULT_TIMEZONE;
        Calendar cal = (Calendar) DateUtils.CALENDAR.clone();
        cal.setLenient(true);
        cal.setTime(date);
        int year = cal.get(1);
        if (cal.get(0) == 0) {
            if (year == 1) {
                stringBuffer.append("+0000");
            } else {
                int isoYear = year - 1;
                stringBuffer.append('-');
                pad4(stringBuffer, isoYear);
            }
        } else {
            if (year > 9999) {
                stringBuffer.append('+');
            }

            pad4(stringBuffer, year);
        }

        stringBuffer.append('-');
        pad2(stringBuffer, cal.get(2) + 1);
        stringBuffer.append('-');
        pad2(stringBuffer, cal.get(5));
        stringBuffer.append(' ');
        pad2(stringBuffer, cal.get(11));
        stringBuffer.append(':');
        pad2(stringBuffer, cal.get(12));
        stringBuffer.append(':');
        pad2(stringBuffer, cal.get(13));
        stringBuffer.append('.');
        pad3(stringBuffer, cal.get(14));

        return stringBuffer;
    }

    @Override
    public Date parse(String s, ParsePosition parsePosition) {
        try {
            return parse(s);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public JSONDateFormat clone() {
        return new JSONDateFormat();
    }

    @Override
    public TimeZone getTimeZone() {
        return _timezone;
    }

    public Date parse(String dateStr) throws ParseException {
        dateStr = StringUtils.trim(dateStr);
        if (StringUtils.isEmpty(dateStr)) {
            return null;
        }

        if (Pattern.compile("^\\d{13}$").matcher(dateStr).matches()) {
            return new Date(Long.valueOf(dateStr));
        } else if (Pattern.compile("^\\d{10}$").matcher(dateStr).matches()) {
            return new Date(Long.valueOf(dateStr + "000"));
        } else {
            for (Map.Entry<String, String> entry : dateRegFormat.entrySet()) {
                if (Pattern.compile(entry.getKey()).matcher(dateStr).matches()) {
                    return DateUtils.parse(dateStr, entry.getValue());
                }
            }
        }
        return null;
    }

    private static void pad2(StringBuffer buffer, int value) {
        int tens = value / 10;
        if (tens == 0) {
            buffer.append('0');
        } else {
            buffer.append((char)(48 + tens));
            value -= 10 * tens;
        }

        buffer.append((char)(48 + value));
    }

    private static void pad3(StringBuffer buffer, int value) {
        int h = value / 100;
        if (h == 0) {
            buffer.append('0');
        } else {
            buffer.append((char)(48 + h));
            value -= h * 100;
        }

        pad2(buffer, value);
    }

    private static void pad4(StringBuffer buffer, int value) {
        int h = value / 100;
        if (h == 0) {
            buffer.append('0').append('0');
        } else {
            if (h > 99) {
                buffer.append(h);
            } else {
                pad2(buffer, h);
            }

            value -= 100 * h;
        }

        pad2(buffer, value);
    }
}
